import itertools
import json
import time
import random
from multiprocessing import Pool, cpu_count
from so import lp_problem

class Solution:
    # 全天的系数 目标值
    default_max_all = [1.05,  1.2, 1.2, 1.2]
    default_min_all = [0.95, 0.95, 0.95, 0.95]
    # 早餐能量占比系数
    # 不参与线性规划 最小值为直接下限
    default_max1 = [0.3, 0.4, 0.4, 0.4]
    default_min1 = [0.2, 0.2, 0.2, 0.2]
    # 午餐能量占比系数
    # 参与线性规划 不设最小值
    default_max2 = [0.4, 0.6, 0.6, 0.6]
    default_min2 = [0, 0, 0, 0]
    # 晚餐能量占比系数
    # 参与线性规划 不设最小值
    default_max3 = [0.3, 0.4, 0.4, 0.4]
    default_min3 = [0, 0, 0, 0]

    def __init__(self, min_all=default_min_all, max_all=default_max_all, min1=default_min1, max1=default_max1,
                min2=default_min2, max2=default_max2, min3=default_min3, max3=default_max3):
        self.min_all = min_all

        self.max_all = max_all
        self.min1 = min1
        self.max1 = max1
        self.min2 = min2
        self.max2 = max2
        self.min2 = min2
        self.max3 = max3
        self.min3 = min3

    def handler(self, first):
        itemContent = first['itemContent']

        a = itemContent.get('早餐')['recipes_items']
        b = itemContent.get('午餐')['recipes_items']
        c = itemContent.get('晚餐')['recipes_items']
        o = first.get('energy')
        print(o)
        calorie = o.get('calorie')
        protein = o.get('protein')
        fat = o.get('fat')
        carbohydrate = o.get('carbohydrate')

        target = {
            'calorie': calorie,
            'protein': protein,
            'fat': fat,
            'carbohydrate': carbohydrate
        }

        a1, a2 = self.dish_filter(a, self.max1, target)
        b1, b2 = self.dish_filter(b, self.max2, target)
        c1, c2 = self.dish_filter(c, self.max3, target)

        print(len(a1))
        print(len(b1))
        print(len(c1))

        # 获取所有组合
        breakfast = list(itertools.combinations(a1, 2)) + list(itertools.combinations(a1, 1)) + list(itertools.combinations(a1, 3))
        lunch = list(itertools.combinations(b1, 3)) + list(itertools.combinations(b1, 2))
        dinner = list(itertools.combinations(c1, 2)) + list(itertools.combinations(c1, 1))

        print(len(breakfast))
        print(len(lunch))
        print(len(dinner))
        result_a = self.result_filter(breakfast, self.min1, self.max1, target)
        result_b = self.result_filter(lunch, self.min2, self.max2, target)
        result_c = self.result_filter(dinner, self.min3, self.max3, target)

        r = 1.25
        while(len(result_a) == 0 and r < 3.1):
            breakfast1 = [self.expand(item, r) for item in breakfast]
            result_a = self.result_filter(breakfast1, self.min1, self.max1, target)
            r += 0.25
            print(len(result_a))
        if (len(result_b) < 51):
            lunch = list(itertools.combinations(b1, 4))
            result_b = self.result_filter(lunch, self.min2, self.max2, target)
        if (len(result_c) < 51):
            dinner = list(itertools.combinations(c1, 3))
            result_c = self.result_filter(dinner, self.min3, self.max3, target)
        print('结果')
        print(len(result_a))
        print(len(result_b))
        print(len(result_c))

        if (len(result_a) == 0 or len(result_b) == 0 or len(result_c) == 0):
            return None

        if (len(result_b) > 100):
            result_b = random.sample(result_b, 50)
        if (len(result_c) > 100):
            result_c = random.sample(result_c, 50)

        print('结果')
        print(len(result_a))
        print(len(result_b))
        print(len(result_c))

        result = self.find_target(result_a, result_b, result_c, self.min_all, self.max_all, target)
        if result is None:
            return None
        else:
            a, b, c, now, ratio, r = result

            print(r)
            a = [{'ratio': 1} | item for item in a]
            for i in range(len(r)):
                if i <= len(b) - 1:
                    b[i]['ratio'] = r[i]
                else:
                    c[i - len(b)]['ratio'] = r[i]


            a = self.result_correct(a)
            b = self.result_correct(b)
            c = self.result_correct(c)

            now = {
                'calorie': 0,
                'protein': 0,
                'fat': 0,
                'carbohydrate': 0
            }
            for item_a in a:
                now['calorie'] += item_a['Calorie'] * item_a['ratio']
                now['protein'] += item_a['Protein'] * item_a['ratio']
                now['fat'] += item_a['Fat'] * item_a['ratio']
                now['carbohydrate'] += item_a['Carbohydrate'] * item_a['ratio']
            for item_b in b:
                now['calorie'] += item_b['Calorie'] * item_b['ratio']
                now['protein'] += item_b['Protein'] * item_b['ratio']
                now['fat'] += item_b['Fat'] * item_b['ratio']
                now['carbohydrate'] += item_b['Carbohydrate'] * item_b['ratio']
            for item_c in c:
                now['calorie'] += item_c['Calorie'] * item_c['ratio']
                now['protein'] += item_c['Protein'] * item_c['ratio']
                now['fat'] += item_c['Fat'] * item_c['ratio']
                now['carbohydrate'] += item_c['Carbohydrate'] * item_c['ratio']
            print(now)

            return {
                'day': first.get('day'),
                'breakfast': list(a),
                'lunch': list(b),
                'dinner': list(c),
                'target': target,
                'result': now,
                'result_ratio': ratio
            }

    def expand(self, item, r):
        items = []
        for obj in item:
            new_obj = obj.copy()
            new_obj['Calorie'] = obj['Calorie'] * r
            new_obj['Protein'] = obj['Protein'] * r
            new_obj['Fat'] = obj['Fat'] * r
            new_obj['Carbohydrate'] = obj['Carbohydrate'] * r
            new_obj['Per_Edible'] = obj['Per_Edible'] * r
            items.append(new_obj)
        return items
    def read_json(self, path):
        with open(path, 'r', encoding='utf-8') as f:
            content = json.load(f)
            return content


    def write_json(self, result_arr, path):
        with open(path, 'w', encoding='utf-8') as file:
            json.dump(result_arr, file, ensure_ascii=False, indent=4)
            print('已输出结论')

    def find_min(self, arr):
        arr2 = list(map(lambda d: d['Calorie'], arr))
        i = arr2.index(min(arr2))
        print(arr[i])
        return arr[i]

    # 过滤菜品，过滤掉单菜超上限的菜
    def dish_filter(self, arr, max, target):
        a1 = []
        a2 = []
        for key, value in arr.items():
            obj = {'name': key}
            # setattr(obj, 'name', key)
            obj.update(value)
            a2.append(obj)
            if obj['Calorie'] < target['calorie'] * max[0] and obj['Protein'] < target['protein'] * max[1] and obj[
                'Fat'] < target['fat'] * max[2] and obj['Carbohydrate'] < target['carbohydrate'] * max[3]:
                a1.append(obj)

        if len(a1) == 0:
            item = self.find_min(a2)
            a1.append(item)

        return a1, a2

    # 二次过滤 早中晚的能量占比 筛选
    def result_filter(self, arr, min, max, target):
        result_a = []
        for a in arr:
            calorie_a = 0
            protein_a = 0
            fat_a = 0
            carbohydrate_a = 0
            for a_1 in a:
                calorie_a += a_1['Calorie']
                protein_a += a_1['Protein']
                fat_a += a_1['Fat']
                carbohydrate_a += a_1['Carbohydrate']
            if target['calorie'] * min[0] <= calorie_a < target['calorie'] * max[0] and target['protein'] * min[
                1] <= protein_a < target['protein'] * max[1] and target['fat'] * min[2] <= fat_a < target['fat'] * \
                    max[
                        2] and target['carbohydrate'] * min[3] <= carbohydrate_a < target['carbohydrate'] * max[3]:
                result_a.append(a)

        return result_a

    # 结果判断
    def judge(self, a, b, c, min, max, target, r=None):
        if r is None:
            r = [1] * (len(b) + len(c))

        now = {
            'calorie': 0,
            'protein': 0,
            'fat': 0,
            'carbohydrate': 0
        }
        for item_a in a:
            now['calorie'] += item_a['Calorie']
            now['protein'] += item_a['Protein']
            now['fat'] += item_a['Fat']
            now['carbohydrate'] += item_a['Carbohydrate']
        for i_b, item_b in enumerate(b):
            now['calorie'] += item_b['Calorie'] * r[i_b]
            now['protein'] += item_b['Protein'] * r[i_b]
            now['fat'] += item_b['Fat'] * r[i_b]
            now['carbohydrate'] += item_b['Carbohydrate'] * r[i_b]
        calorie3 = 0
        for i_c, item_c in enumerate(c):
            idx = i_c + len(b)
            calorie3 += item_c['Calorie'] * r[idx]
            now['calorie'] += item_c['Calorie'] * r[idx]
            now['protein'] += item_c['Protein'] * r[idx]
            now['fat'] += item_c['Fat'] * r[idx]
            now['carbohydrate'] += item_c['Carbohydrate'] * r[idx]
        if calorie3 > target['calorie'] * 0.3:
            return None

        # print(now['calorie'] / target['calorie'], now['protein'] / target['protein'],
        #       now['fat'] / target['fat'], now['carbohydrate'] / target['carbohydrate'])
        if target['calorie'] * min[0] <= now['calorie'] < target['calorie'] * max[0] and target['protein'] * min[
            1] <= now['protein'] < target['protein'] * max[1] and target['fat'] * min[2] <= now['fat'] < target[
            'fat'] * max[2] and target['carbohydrate'] * min[3] <= now['carbohydrate'] < target['carbohydrate'] * \
                max[3]:

            ratio = {
                'calorie': now['calorie'] / target['calorie'],
                'protein': now['protein'] / target['protein'],
                'fat': now['fat'] / target['fat'],
                'carbohydrate': now['carbohydrate'] / target['carbohydrate']
            }

            return a, b, c, now, ratio
        else:
            return None

    @staticmethod
    def _judge2(args):
        instance, a, b, result_c, min_all, max_all, target = args
        count = 0

        min_item = {
            "a": None,
            'b': None,
            'c': None,
            'now': None,
            'ratio': None,
            'r': None
        }
        min_ratio = 0

        for idx, c in enumerate(result_c):
            # 初筛 减小进入线性规划的量
            result = instance.judge(a, b, c, [0.2, 0.2, 0.2, 0.2], [1, 1, 1, 1], target)
            if result is None:
                continue
            else:
                count += 1

            r = lp_problem(a, b, c, target)
            if r is None:
                continue
            else:
                now, ratio = instance.count_power(a, b, c, target, r)
                if now['protein'] < target['protein'] * max_all[1] and now['fat'] < target['fat'] * max_all[
                    2] and now['carbohydrate'] < target['carbohydrate'] * max_all[3]:
                    return a, b, c, now, ratio, r, 'target', count
                else:
                    c_ratio = (ratio['protein'] - 1) ** 2 + (ratio['fat'] - 1) ** 2 + (ratio['carbohydrate'] - 1) ** 2
                    if min_ratio == 0:
                        min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
                        min_ratio = c_ratio
                    elif c_ratio < min_ratio:
                        min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
                        min_ratio = c_ratio

            # # 最终筛选 返回结论
            # ratios = instance.count_ratio(len(b) + len(c))
            #
            # for r in ratios:
            #     result = instance.judge2(a, b, c, min_all, max_all, target, r)
            #     if result is None:
            #         continue
            #     else:
            #         a, b, c, now, ratio = result
            #         if now['protein'] < target['protein'] * max_all[1] and now['fat'] < target['fat'] * max_all[
            #             2] and now['carbohydrate'] < target['carbohydrate'] * max_all[3]:
            #             return a, b, c, now, ratio, r, 'target', count
            #         else:
            #             c_ratio = abs(ratio['protein'] ** 2 + ratio['fat'] - 1) ** 2 + abs(ratio['carbohydrate'] - 1) ** 2
            #             if min_ratio == 0:
            #                 min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
            #                 min_ratio = c_ratio
            #             elif c_ratio < min_ratio:
            #                 min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
            #                 min_ratio = c_ratio

        if (min_ratio > 0):
            return min_item['a'], min_item['b'], min_item['c'], min_item['now'], min_item['ratio'], min_item[
                'r'], 'min', count
        else:
            return None

    def judge2(self, a, b, c, min, max, target, r=None):
        if r is None:
            r = [1] * (len(b) + len(c))

        now = {
            'calorie': 0,
            'protein': 0,
            'fat': 0,
            'carbohydrate': 0
        }
        for item_a in a:
            now['calorie'] += item_a['Calorie']
            now['protein'] += item_a['Protein']
            now['fat'] += item_a['Fat']
            now['carbohydrate'] += item_a['Carbohydrate']
        for i_b, item_b in enumerate(b):
            now['calorie'] += item_b['Calorie'] * r[i_b]
            now['protein'] += item_b['Protein'] * r[i_b]
            now['fat'] += item_b['Fat'] * r[i_b]
            now['carbohydrate'] += item_b['Carbohydrate'] * r[i_b]
        calorie3 = 0
        for i_c, item_c in enumerate(c):
            idx = i_c + len(b)
            calorie3 += item_c['Calorie'] * r[idx]
            now['calorie'] += item_c['Calorie'] * r[idx]
            now['protein'] += item_c['Protein'] * r[idx]
            now['fat'] += item_c['Fat'] * r[idx]
            now['carbohydrate'] += item_c['Carbohydrate'] * r[idx]
        if calorie3 > target['calorie'] * 0.3:
            return None

        if target['calorie'] * min[0] <= now['calorie'] < target['calorie'] * max[0] and target['protein'] * min[
            1] <= now['protein'] and target['fat'] * min[2] <= now['fat'] and target['carbohydrate'] * min[3] <= now['carbohydrate']:

            ratio = {
                'calorie': now['calorie'] / target['calorie'],
                'protein': now['protein'] / target['protein'],
                'fat': now['fat'] / target['fat'],
                'carbohydrate': now['carbohydrate'] / target['carbohydrate']
            }

            return a, b, c, now, ratio
        else:
            return None

    # 穷举系数
    def count_ratio(self, length):
        ratios = [1, 1.25, 1.5, 2, 2.5, 3]
        # ratios = [1]
        return list(itertools.product(ratios, repeat=length))

    def find_target(self, result_a, result_b, result_c, min_all, max_all, target):
        i = 0
        count = 0

        min_item = {
            "a": None,
            'b': None,
            'c': None,
            'now': None,
            'ratio': None,
            'r': None
        }
        min_ratio = 0

        a = random.choice(result_a)
        chunks = [
            (self, a, b, result_c, min_all, max_all, target)
            for b in result_b
        ]

        num_processes = min(cpu_count(), 4)

        with Pool(processes=num_processes) as pool:
            for result in pool.imap_unordered(self._judge2, chunks):
                if result is not None:
                    a, b, c, now, ratio, r, t, _count = result
                    print(_count)
                    count += _count
                    print(count)
                    if (t == 'target'):
                        pool.terminate()
                        pool.close()
                        pool.join()
                        return a, b, c, now, ratio, r
                    else:
                        c_ratio = abs(ratio['fat'] - 1) ** 2 + abs(ratio['carbohydrate'] - 1) ** 2
                        if min_ratio == 0:
                            min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
                            min_ratio = c_ratio
                        elif c_ratio < min_ratio:
                            min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
                            min_ratio = c_ratio

        print(count)
        pool.close()
        pool.join()
        if min_ratio == 0:
            return None
        else:
            return min_item['a'], min_item['b'], min_item['c'], min_item['now'], min_item['ratio'], min_item[
                'r']

        # a = result_a[0]
        # for b in result_b:
        #     for c in result_c:
        #         i += 1
        #         print(i)
        #
        #         # 初筛 减小进入线性规划的量
        #         result = self.judge(a, b, c, [0.2, 0.2, 0.2, 0.2], [1, 1, 1, 1], target)
        #         if result is None:
        #             continue
        #         else:
        #             count += 1
        #
        #         # 最终筛选 返回结论
        #         ratios = self.count_ratio(len(b) + len(c))
        #
        #         for r in ratios:
        #             result = self.judge2(a, b, c, min_all, max_all, target, r)
        #             if result is None:
        #                 continue
        #             else:
        #                 a, b, c, now, ratio = result
        #                 if now['protein'] < target['protein'] * max_all[1] and now['fat'] < target['fat'] * max_all[2] and now['carbohydrate'] < target['carbohydrate'] * max_all[3]:
        #                     return a, b, c, now, ratio, r
        #                 else:
        #                     c_ratio = abs(ratio['protein'] ** 2 + ratio['fat'] - 1) ** 2 + abs(ratio['carbohydrate'] - 1) ** 2
        #                     if min_ratio == 0:
        #                        min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
        #                        min_ratio = c_ratio
        #                     elif c_ratio < min_ratio:
        #                        min_item.update({'a': a, 'b': b, 'c': c, 'now': now, 'ratio': ratio, 'r': r})
        #                        min_ratio = c_ratio

        # print(count)
        # if min_ratio > 0:
        #     return min_item['a'], min_item['b'], min_item['c'], min_item['now'], min_item['ratio'], min_item['r']
        # else:
        #     return None

    def result_correct(self, list):
        for item in list:
            r = 100 / item['Per_Edible']
            item['Calorie'] = item['Calorie'] * r
            item['Protein'] = item['Protein'] * r
            item['Fat'] = item['Fat'] * r
            item['Carbohydrate'] = item['Carbohydrate'] * r
            item['ratio'] = item['ratio'] / r
            item['Per_Edible'] = 100
        return list

    def data_correct_eat(self, obj):
        for key, value in obj.items():
            if value['Per_Edible'] < 11:
                obj[key]['Calorie'] = obj[key]['Calorie'] * 10
                obj[key]['Protein'] = obj[key]['Protein'] * 10
                obj[key]['Fat'] = obj[key]['Fat'] * 10
                obj[key]['Per_Edible'] = obj[key]['Per_Edible'] * 10
            elif value['Per_Edible'] < 21:
                obj[key]['Calorie'] = obj[key]['Calorie'] * 5
                obj[key]['Protein'] = obj[key]['Protein'] * 5
                obj[key]['Fat'] = obj[key]['Fat'] * 5
                obj[key]['Per_Edible'] = obj[key]['Per_Edible'] * 5
            elif value['Per_Edible'] < 34:
                obj[key]['Calorie'] = obj[key]['Calorie'] * 3
                obj[key]['Protein'] = obj[key]['Protein'] * 3
                obj[key]['Fat'] = obj[key]['Fat'] * 3
                obj[key]['Per_Edible'] = obj[key]['Per_Edible'] * 3
            elif value['Per_Edible'] < 51:
                obj[key]['Calorie'] = obj[key]['Calorie'] * 2
                obj[key]['Protein'] = obj[key]['Protein'] * 2
                obj[key]['Fat'] = obj[key]['Fat'] * 2
                obj[key]['Per_Edible'] = obj[key]['Per_Edible'] * 2


    def data_correct(self, first):
        itemContent = first['itemContent']

        a = itemContent.get('早餐')['recipes_items']
        b = itemContent.get('午餐')['recipes_items']
        c = itemContent.get('晚餐')['recipes_items']

        self.data_correct_eat(a)
        self.data_correct_eat(b)
        self.data_correct_eat(c)

        print(first)

        return first


    def count_power(self, a, b, c, target, r):
        now = {
            'calorie': 0,
            'protein': 0,
            'fat': 0,
            'carbohydrate': 0
        }
        for item_a in a:
            now['calorie'] += item_a['Calorie']
            now['protein'] += item_a['Protein']
            now['fat'] += item_a['Fat']
            now['carbohydrate'] += item_a['Carbohydrate']
        for i_b, item_b in enumerate(b):
            now['calorie'] += item_b['Calorie'] * r[i_b]
            now['protein'] += item_b['Protein'] * r[i_b]
            now['fat'] += item_b['Fat'] * r[i_b]
            now['carbohydrate'] += item_b['Carbohydrate'] * r[i_b]
        for i_c, item_c in enumerate(c):
            idx = i_c + len(b)
            now['calorie'] += item_c['Calorie'] * r[idx]
            now['protein'] += item_c['Protein'] * r[idx]
            now['fat'] += item_c['Fat'] * r[idx]
            now['carbohydrate'] += item_c['Carbohydrate'] * r[idx]

        ratio = {
            'calorie': now['calorie'] / target['calorie'],
            'protein': now['protein'] / target['protein'],
            'fat': now['fat'] / target['fat'],
            'carbohydrate': now['carbohydrate'] / target['carbohydrate']
        }
        return now, ratio


if __name__ == '__main__':
    s = Solution()
    content = s.read_json('data9.json')
    result_arr = []

    start_time = time.time()
    first = content[2]
    first = s.data_correct(first)
    result = s.handler(first)
    print(result)
    # for first in content:
    #     first = s.data_correct(first)
    #     result = s.handler(first)
    #     if result is not None:
    #         result_arr.append(result)
    #         continue
    #     else:
    #         print('未找到')

    s.write_json(result_arr, 'log.json')
    end_time = time.time()
    print(end_time - start_time)
    print(result_arr)
